package pfq.demo.threads;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 下面是一道我在今日头条上看到的一道面试题：
 * 使用多线程计算1到1000000以内的所有素数的个数。
 * <p>
 * 这道题不难，使用线程池Executors.newFixedThreadPool()即可解决
 * <p>
 * 但发现些问题：
 * 1. 通过Future.get()方法可以获取Callable返回的结果，但需要注意的是会阻塞当前线程，这个在Android编程中切忌在主线程中调用
 * 2. 在设置多线程的线程数时，我刚开始设置的4个，发现不管数字改成多少，多线程执行的时间都比单线程执行的时间多，这就很纳闷了。
 * 后来发现其实是因为我的mac的cpu核心数只有2个，当我将线程数改为2后，多线程执行的时间立即下降了很多，比单线程快多了，
 * 由此得出一个结论：在多线程编程中，线程数的设置是有讲究的，并不是越多越好，而是最接近当前设备CPU的核心数是最好的
 */
public class PrimeNum {

    /**
     * 判断传入的数字是否为素数
     *
     * @param num 数字
     * @return 是否为素数
     */
    public boolean isPrime(int num) {
        if (num <= 1) {
            return false;
        }

        int numNew = (int) Math.sqrt(num);
        // 素数：只有1和本身两个因子的自然数（1不是素数）
        // 所以可以判断从2开始到本身中间的数，是否存在可以被该数整除的数，如果有则说明不是素数
        for (int i = 2; i <= numNew; i++) {
            if (num % i == 0) {
                return false;
            }
        }

        return true;
    }


    /**
     * 计算从start数字开始到end数字结束之间共有多少个素数
     *
     * @param startNum 开始数字
     * @param endNum   结束数字
     * @return
     */
    public int calculate(int startNum, int endNum) {
        int count = 0;
        for (int i = startNum; i <= endNum; i++) {
            if (isPrime(i)) {
                count++;
            }
        }

        return count;
    }

    class Task implements Callable<Integer> {
        int startNum;
        int endNum;

        Task(int startNum, int endNum) {
            this.startNum = startNum;
            this.endNum = endNum;
        }

        @Override
        public Integer call() throws Exception {
            return calculate(startNum, endNum);
        }
    }

    static final int num = 10000000;

    public void single() {
        // 单线程
        ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
        System.out.println("单线程执行开始：");
        long startTime = System.currentTimeMillis();
        Future<Integer> singleFuture = singleExecutorService.submit(new Task(1, num));
        singleExecutorService.shutdown();
        try {
            int count = singleFuture.get();
            long endTime = System.currentTimeMillis() - startTime;
            System.out.println("共有素数：" + count + "个");
            System.out.println("耗时：" + endTime / 1000f + "s");
            System.out.println("单线程执行结束。");
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void multiple() {
        final int threadCount = 2;
        ExecutorService multipleExecutorService = Executors.newFixedThreadPool(threadCount);
        List<Callable<Integer>> callables = new ArrayList<>();
        for (int i = 1; i <= threadCount; i++) {
            Callable callable = new Task(1 + num / threadCount * (i - 1), num / threadCount * i);
            callables.add(callable);
        }
        System.out.println("多线程执行开始：");
        long startTime1 = System.currentTimeMillis();
        try {
            List<Future<Integer>> result = multipleExecutorService.invokeAll(callables, 10000, TimeUnit.SECONDS);

            int count = 0;
            for (int i = 0; i < result.size(); i++) {
                count += result.get(i).get();
            }

            System.out.println("共有素数：" + count + "个");
            long endTime = System.currentTimeMillis() - startTime1;
            System.out.println("耗时：" + endTime / 1000f + "s");
            System.out.println("多线程执行结束。");

            multipleExecutorService.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        PrimeNum primeNum = new PrimeNum();
        primeNum.single();
        System.out.println("===========================");
        primeNum.multiple();

    }

}
